package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.client.user.UserClient;
import com.tianji.api.dto.user.UserDTO;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constant.RedisConstants;
import com.tianji.learning.domain.po.PointsBoard;
import com.tianji.learning.domain.query.PointsBoardQuery;
import com.tianji.learning.domain.vo.PointsBoardItemVO;
import com.tianji.learning.domain.vo.PointsBoardVO;
import com.tianji.learning.mapper.PointsBoardMapper;
import com.tianji.learning.service.PointsBoardService;
import com.tianji.learning.utils.TableInfoContext;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.tianji.learning.constant.LearningConstants.POINTS_BOARD_TABLE_PREFIX;


/**
 * <p>
 * 学霸天梯榜 服务实现类
 * </p>
 *
 */
@Service
@RequiredArgsConstructor
public class PointsBoardServiceImpl extends ServiceImpl<PointsBoardMapper, PointsBoard> implements PointsBoardService {

    private final StringRedisTemplate redisTemplate;
    private final UserClient userClient;

    /**
     * 根据赛季 ID 创建积分排行榜表
     * @param season
     */
    @Override
    public void createPointsBoardTableBySeason(Integer season) {
        getBaseMapper().createPointsBoardTable(POINTS_BOARD_TABLE_PREFIX + season);
    }

    /**
     * 分页查询指定赛季的积分排行榜
     *
     * @param pointsBoardQuery 积分排行榜查询条件对象，包含查询所需的参数，如赛季、页码、每页大小等
     * @return 封装好的积分排行榜视图对象，包含用户的排名、积分以及排行榜列表信息
     */
    @Override
    public PointsBoardVO queryPointsBoardBySeason(PointsBoardQuery pointsBoardQuery) {
        // 1. 判断是否是查询当前赛季
        // 从查询条件中获取赛季信息
        Long season = pointsBoardQuery.getSeason();
        // 如果赛季为 null 或者为 0，则认为是查询当前赛季
        boolean isCurrent = season == null || season == 0;

        // 2. 获取 Redis 的 Key
        // 获取当前时间
        LocalDateTime now = LocalDateTime.now();
        // 拼接 Redis 中存储积分排行榜的键，格式为前缀 + 当前时间格式化后的字符串
        String key = RedisConstants.POINTS_BOARD_KEY_PREFIX + now.format(DateUtils.POINTS_BOARD_SUFFIX_FORMATTER);

        // 3. 查询我的积分和排名
        // 如果是查询当前赛季，从 Redis 中查询我的积分和排名
        // 如果是查询历史赛季，从 MySQL 中查询我的积分和排名
        PointsBoard myBoard = isCurrent ? queryMyCurentBoard(key) // 查看当前榜单(Redis)
                : queryMyHistoryBoard(key); // 查看历史榜单(mysql)

        // 4. 查询榜单列表
        // 如果是查询当前赛季，从 Redis 中分页查询榜单列表
        // 如果是查询历史赛季，从 MySQL 中查询历史榜单列表
        List<PointsBoard> list = isCurrent ? queryCurrentBoardList(key, pointsBoardQuery.getPageNo(), pointsBoardQuery.getPageSize())
                : queryHistoryBoardList(pointsBoardQuery);

        // 5. 封装 VO
        PointsBoardVO pointsBoardVO = new PointsBoardVO();
        // 5.1 处理我的信息
        // 如果查询到了我的积分和排名信息
        if (myBoard != null) {
            // 将我的排名设置到视图对象中
            pointsBoardVO.setRank(myBoard.getRank());
            // 将我的积分设置到视图对象中
            pointsBoardVO.setPoints(myBoard.getPoints());
        }
        // 如果榜单列表为空，直接返回封装好的视图对象
        if (CollUtils.isEmpty(list)) {
            return pointsBoardVO;
        }
        // 5.2 查询用户信息
        // 从榜单列表中提取所有用户的 ID 并存储到一个集合中
        Set<Long> uIds = list.stream()
                .map(PointsBoard::getUserId)
                .collect(Collectors.toSet());
        // 调用用户服务客户端，根据用户 ID 集合查询用户信息
        List<UserDTO> users = userClient.queryUserByIds(uIds);
        // 创建一个用于存储用户 ID 和用户名映射关系的 Map
        Map<Long, String> userMap = new HashMap<>(uIds.size());
        // 如果查询到了用户信息
        if (CollUtils.isNotEmpty(users)) {
            // 将用户信息转换为 ID 到用户名的映射关系
            userMap = users.stream()
                    .collect(Collectors.toMap(UserDTO::getId, UserDTO::getName));
        }
        // 5.3 转换 VO
        // 创建一个用于存储转换后的视图项的列表
        List<PointsBoardItemVO> items = new ArrayList<>(list.size());
        // 遍历榜单列表
        for (PointsBoard pointsBoard : list) {
            // 创建一个新的积分排行榜视图项对象
            PointsBoardItemVO pointsBoardItemVO = new PointsBoardItemVO();
            // 这里存在错误，应该是设置当前用户的排名，而不是 pointsBoardVO 的排名
            // 修正为：pointsBoardItemVO.setRank(pointsBoard.getRank());
            pointsBoardItemVO.setRank(pointsBoard.getRank());
            // 这里存在错误，应该是设置当前用户的积分
            // 修正为：pointsBoardItemVO.setPoints(pointsBoard.getPoints());
            pointsBoardItemVO.setPoints(pointsBoard.getPoints());
            // 根据用户 ID 从用户映射中获取用户名并设置到视图项中
            pointsBoardItemVO.setName(userMap.get(pointsBoard.getUserId()));
            // 将视图项添加到列表中
            items.add(pointsBoardItemVO);
        }
        // 将转换后的视图项列表设置到积分排行榜视图对象中
        pointsBoardVO.setBoardList(items);
        // 返回封装好的积分排行榜视图对象
        return pointsBoardVO;
    }

    /**
     * 查看当前榜单(Redis)
     * @param key
     * @return
     */
    private PointsBoard queryMyCurentBoard(String key) {
        // 1.绑定key
        BoundZSetOperations<String, String> ops = redisTemplate.boundZSetOps(key);
        // 2.获取当前用户信息
        String userId = UserContext.getUser().toString();
        // 3.查询积分
        Double points = ops.score(userId);
        // 4.查询排名
        Long rank = ops.reverseRank(userId);
        // 5.封装返回
        PointsBoard p = new PointsBoard();
        p.setPoints(points == null ? 0 : points.intValue());
        p.setRank(rank == null ? 0 : rank.intValue() + 1);
        return p;
    }

    /**
     * 查看历史榜单(mysql)
     * @param key
     * @return
     */
    private PointsBoard queryMyHistoryBoard(String key) {
        // 1.获取登录用户
        Long userId = UserContext.getUser();
        // 2.计算表名
        TableInfoContext.setInfo(POINTS_BOARD_TABLE_PREFIX + key);
        // 3.查询数据
        Optional<PointsBoard> opt = lambdaQuery().eq(PointsBoard::getUserId, userId).oneOpt();
        if (opt.isEmpty()) {
            return null;
        }
        // 4.转换数据
        PointsBoard pointsBoard = opt.get();
        pointsBoard.setRank(pointsBoard.getId().intValue());
        return pointsBoard;
    }

    /**
     * todo:如果是查询当前赛季，从 Redis 中查询我的积分和排名
     * @param key
     * @param pageNo
     * @param pageSize
     * @return
     */
    @Override
    public List<PointsBoard> queryCurrentBoardList(String key, int pageNo, int pageSize) {
        // 1. 计算分页的起始索引
        // 由于Redis的有序集合索引是从0开始的，所以需要根据页码和每页大小来计算起始索引
        // 例如，pageNo为1，pageSize为10，那么from就是0，即从第1条数据开始
        // pageNo为2，pageSize为10，from就是10，即从第11条数据开始
        int from = (pageNo - 1) * pageSize;

        // 2. 从Redis中查询数据
        // 使用redisTemplate的opsForZSet方法来操作Redis的有序集合
        // reverseRangeWithScores方法会按照分数从高到低的顺序，从指定的起始索引到结束索引（包含）查询数据
        // 结束索引是from + pageSize - 1，确保查询的数据条数为pageSize
        // 查询结果是一个Set集合，集合中的每个元素是一个TypedTuple对象，包含了成员和对应的分数
        Set<ZSetOperations.TypedTuple<String>> tuples = redisTemplate.opsForZSet()
                .reverseRangeWithScores(key, from, from + pageSize - 1);

        // 3. 封装查询结果到PointsBoard对象列表中
        // 初始化排名，排名从起始索引 + 1开始，因为排名是从1开始计数的
        int rank = from + 1;
        // 创建一个ArrayList来存储PointsBoard对象，初始容量设置为查询结果的大小
        List<PointsBoard> list = new ArrayList<>(tuples.size());

        // 遍历查询结果的每个元素
        for (ZSetOperations.TypedTuple<String> tuple : tuples) {
            // 从TypedTuple对象中获取成员（这里代表用户ID）
            String userId = tuple.getValue();
            // 从TypedTuple对象中获取分数（这里代表用户的积分）
            Double points = tuple.getScore();

            // 进行空值检查，如果用户ID或积分是null，则跳过当前元素，继续处理下一个元素
            if (userId == null || points == null) {
                continue;
            }

            // 创建一个新的PointsBoard对象
            PointsBoard p = new PointsBoard();
            // 将用户ID转换为Long类型并设置到PointsBoard对象中
            p.setUserId(Long.valueOf(userId));
            // 将积分转换为int类型并设置到PointsBoard对象中
            p.setPoints(points.intValue());
            // 设置当前用户的排名，然后排名自增1
            p.setRank(rank++);

            // 将PointsBoard对象添加到列表中
            list.add(p);
        }

        // 返回封装好的PointsBoard对象列表
        return list;
    }

    /**
     * 如果是查询历史赛季，从 MySQL 中查询历史榜单列表
     * @param pointsBoardQuery
     * @return
     */
    private List<PointsBoard> queryHistoryBoardList(PointsBoardQuery pointsBoardQuery) {
        // 1.计算表名
        TableInfoContext.setInfo(POINTS_BOARD_TABLE_PREFIX + pointsBoardQuery.getSeason());
        // 2.查询数据
        Page<PointsBoard> page = page(pointsBoardQuery.toMpPage());
        // 3.数据处理
        List<PointsBoard> records = page.getRecords();
        if (CollUtils.isEmpty(records)) {
            return CollUtils.emptyList();
        }
        records.forEach(b -> b.setRank(b.getId().intValue()));
        return records;
    }


}
